Gyazo と GKE
Nota Tech Conf 2022 Spring
2022 Spring Day1 タイムテーブル
2022-05-17 (Tue) 19:55 スタート予定 (15m)
準備
30分ぐらい前にたたかうマヌカを舐める
hiroshi
https://github.com/hiroshi
Gyazo のメンテや開発をやってます
おもにバックエンド関連 (Rails, golang, etc...)やたまに DevOps
今日は DevOps のお話
Gyazo と GKE
deploy
branch staging
Gyazo と GKE
Gyazo は GKE 上で動いています
GKE とは GCP (Google Cloud Platform) で提供されている Kubernetes の managed service
GCP とか Kubernetes (k8s) が何かはなんとなくわかっている前提で話をします
GKE cluster は production (gyazo.com) と staging で分けています
staging cluster では branch staging と呼ばれているものが動いている
他にもイントラ系 kibana, grafana などが動いている GKE cluster もある...
なぜ GKE (Kubernetes)?
Gyazo の初期のころは素朴にレンタルサーバー的なホスティングのマシン上で動いていた
直近では GCE の instance template から instance group 作成
docker for mac が登場して開発者のローカル環境は docker-compose に
rbenv や nvm が必要無いので楽
deploy 先も コンテナ化 or Full managed な PaaS に移行して楽をしたい
ruby 以外にもフロントエンドのビルドには node.js のツールが必要
コンテナにすると最終 image は node.js なしで済む
サーバーホスト管理したくない...
アップロードや画像配信のアプリケーションは分かれていてやや複雑
メインは Rails だけど画像バイナリを扱うところは golang
heroku みたいなシンプルな PasS では難しい
アプリケーションコードをほぼそのまま移行するにはその複雑さを再現できる Kubernetes がちょうどよかった
k8s cluster 内 network でお互いにアクセス
最近は Serverless VPC Access で Cloud Run とかから GCP のネットワークに接続とかできるらしいけど...
pod に複数のコンテナ (nginx, ruby server, fluentd, etc...)
一つのコンテナに突っ込むのは辛そう...
Gyazo の GKE cluster はどんな感じに運用されているのか
Cloudbuild で deploy してます
production 環境への入り口を制限したい
GCP 外部の CI から deploy するためには service account key などが必要になる
各CIに self-hosted runner とかあるけど...
Cloudbuild の step としては
普通に docker image をビルドして
kubectl apply で service, deployment などのリソースを更新
deploy で工夫してるところだと...
code:manifest.yaml
kind: Deployment
spec:
template:
metadata:
annotations:
kubernetes.io/change-cause: "${date} TAG=${IMAGE_TAG}"
https://nota.gyazo.com/14a4f6252f1ec8ce3ace4959c32183fa
slack で rollback 方法を通知
https://nota.gyazo.com/3abf1be4b1b039d1c599cb9526752caf
manifest yaml の管理は kustomize を一部使ってますが...
変数展開は envsubst とか使う必要あるし
構成変更するのに yaml のパッチみたいなの書くのですが冗長だし自由度が低い...
単純なところは bash の heredoc 使ったほうが簡単
シンプルな yaml, json 汎用管理ツールみたいなのに切り替えたい...
branch staging とは
heroku review apps とか一般的には development 環境と呼ばれたりしているものを k8s 向けに自前で実装
https://my-branch--g.gyazo.example/ みたいなサブドメインでアクセスすると my-branch のデプロイ先にアクセスできる
branch staging はどういうしくみなのか?
URL ルーティング
scale to zero
branch staging へのリクエスト
https://gyazo.com/64bf0311e7c18adaa7f04f0c6f064782
Google HTTP Load balancer の backend service として staging-proxy という nginx コンテナの service を指定
nginx.conf で --g で終わるサブドメインは upstream .default.svc.cluster.local に流れるように...
e.g. my-branch--g.gyazo.example -> my-branch--g.default.svc.cluster.local
そうすると cluster network の service my-branch--g にリクエストが流れる...
branch staging の scale to zero
production ではふつうに HPA (Horizaontal Pod Autoscaler)
HPA は pod を 0 にできない
どんどんブランチができると GKE cluster の CPU/memory resources を圧迫
手動で kubectl delete deployment my-branch--g とかキビシイ
HPA は 0 から復活することもできない
osiris というのを最初は使っていたけど...
https://github.com/deislabs/osiris
ブランチの数が増えたときに挙動のデバッグが大変であきらめた
今見たら僕の helm3 対応 commit が最後で archive になってる...
https://nota.gyazo.com/dc8f031e1a0e88944d75b64ef5ec79c1
すごいwakix.icon
knative serving (Cloud Run はこれで動いている) でも scale to zero できるみたいだけど...
1 container/ 1 pod という制限があったり (このへん変わるみたいだけど...)
これ書いているときに https://keda.sh/ というのも発見してしまった...
やりたいことは環境によって微妙に違うはず...
k8s API 自体は REST なので難しくない
試しに ruby で実装してみるか... -> branch-manager.rb
branch-manager.rb がやってること
https://gyazo.com/6d3a387787460f34b20d31ba0c705a8a
上記 staging-proxy の nginx.conf で --g の svc に upstream するときに branch-manager にもリクエストを mirror
該当する service の deployment の pod が 0 だったら kubectl scale deployment/my-branch--g --replicas=1 相当の API を叩く
各 service の最終アクセス日時を記録しておいて、一定時間アクセスが無い service は scale --replicas=0
基本はコレだけ
branch-manager.rb の実装
100行ぐらいの ruby script
code:Gemfile
gem "sinatra"
gem 'rest-client'
gem 'rufus-scheduler'
gem 'tzinfo-data' # Internally required by rufus-scheduler
gem 'dalli'
rbac で適切な権限を与えていれば以下のようなリクエストヘッダ設定で API アクセスできる
code:ruby
def resource_client
if ENV'KUBECTL_PROXY'
Thread.current:resource_client ||= RestClient::Resource.new(
ENV'KUBECTL_PROXY',
headers: {
'Content-Type' => 'application/strategic-merge-patch+json'
}
)
else
token = File.read('/var/run/secrets/kubernetes.io/serviceaccount/token')
Thread.current:resource_client ||= RestClient::Resource.new(
'https://kubernetes.default.svc',
ssl_ca_file: '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt',
headers: {
'Authorization' => "Bearer #{token}",
'Content-Type' => 'application/strategic-merge-patch+json'
}
)
end
end
たとえばこんな感じ
code:ruby
res = resource_client"/api/v1/namespaces/default/pods".get(params: {labelSelector: 'branch-manager'})
json = JSON.parse(res.body)
API を調べるには kubectl -v8
https://nota.gyazo.com/193dc56018618932869722ff243da7ff
ローカルでデバッグするときは
code:sh
kubectl --cluster=gyazo-staging proxy
code:out
Starting to serve on 127.0.0.1:8001
おわり
こんな感じに Nota では DevOps 系のタスクもひっそりとやってます
インフラ/セキュリティエンジニア募集してます
https://herp.careers/v1/notainc/9TcdolN1jmk4
最初は helpfeel の DevOps を助けて欲しいけど、長期的にはサービス共通基盤みたいなのができるとかっこいいと思ってる